#include "base.h"
#include "game.h"
#include "text.h"
#include "scene.h"
#include "setting.h"
#include "kinetic.h"
#include "vision.h"

#include <Windows.h>
#include <cstdlib>
#include <ctime>
#include <Shellapi.h>

using namespace std;
using namespace Ogre;

static HANDLE lock;
static HANDLE cv_thread;


int random(int l, int r)
{
	static bool first = true;
	if (first)
	{
		first = false;
		srand(time(NULL));
	}

	if (l > r) return r;
	return l + (rand() * RAND_MAX + rand())%(r-l+1);
}

// Construct the base class

Sounder* Base::get_sounder()
{
	return &sounder;
}
Camera* Base::get_cam()
{
	return cam;
}

Base::Base(void)
{
	window = NULL;
	scene_manager = NULL;
	first_run = false;
	flag = BASE_MAINTAIN;
	root = new Root(PLUGIN_FILE_NAME, CONFIG_FILE_NAME, LOG_FILE_NAME);
}

// Delete the base class

Base::~Base(void)
{
	Ogre::WindowEventUtilities::removeWindowEventListener(window, this);
	if (window != NULL)
		if (!window->isClosed())
			window->destroy();
	delete root;
}

RenderWindow* Base::get_window()
{
	return window;
}

void Base::prepare_res()
{
	Ogre::ConfigFile cf;
	cf.load(RES_FILE_NAME);
	Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
	Ogre::String secName, typeName, archName;
	while (seci.hasMoreElements())
	{
		secName = seci.peekNextKey();
		Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
		Ogre::ConfigFile::SettingsMultiMap::iterator i;
		for (i = settings->begin(); i != settings->end(); ++i)
		{
			typeName = i->first;
			archName = i->second;
			Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
				archName, typeName, secName);
		}
	}
	Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
	Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(3);
}

bool Base::init()
{
	terrain_group = NULL;
	terrain_option = NULL;
	cfg_changed = false;
	text_map.init();
	sounder.init(this);
	if (!load_cfg())
		return false;
	window = root->initialise(true, get_text("CAP_WND"));
	
	prepare_res();

	scene_manager = root->createSceneManager("DefaultSceneManager");
	cam = scene_manager->createCamera("cam");
	viewport = window->addViewport(cam);
	
	viewport->setBackgroundColour(Ogre::ColourValue(0,0,0));
	cam->setAspectRatio(Ogre::Real(viewport->getActualWidth()) / viewport->getActualHeight());
	
	Ogre::WindowEventUtilities::addWindowEventListener(window, this);
	controler.init(this);
	
	root->addFrameListener(this);
	tray_manager = new OgreBites::SdkTrayManager("tray", window, controler.get_mouse(), this);
	if (first_run)
	{
		sounder.load_stream("wait", WAIT_MUS_FILE_NAME, false);
		sounder.play_repeat("wait");
		tray_manager->createLabel(OgreBites::TL_CENTER, "first", get_UTFtext("TEXT_FIRST"), 600);
		tray_manager->hideCursor();
	}
	windowResized(window);
	return root->isInitialised();
}


void Base::main()
{
	generateAllFontTextures();
	
	root->startRendering();
	//TODO
}

bool Base::work_basic(Ogre::Real ftime)
{
	if (window->isClosed())
		return false;

	// Update input devices
	controler.do_capture();

	const OIS::MouseState& ms = controler.get_mouse()->getMouseState();
	int mouse_x = ms.X.abs;
	int mouse_y = ms.Y.abs;
	
	//scene_update_mouse(mouse_x, mouse_y);
	
	static double time = 0.0;
	if (time > 0)
		time -= ftime;
	if (controler.get_keyboard()->isKeyDown(OIS::KC_ESCAPE))
		if (time <= 0)
		{
			time = 0.2;
			sounder.play_once("hit");
			if (flag != BASE_PAUSE && (last_flag == BASE_GAME_NORMAL || last_flag == BASE_START || last_flag == BASE_GAME_PRO))
			{
				flag = BASE_PAUSE;
				tray_manager->showYesNoDialog(get_UTFtext("TEXT_PCAP"), get_UTFtext("TEXT_P"));
			}
			else if (flag == BASE_PAUSE)
			{
				flag = BASE_MAINTAIN;
				if (last_flag != BASE_OPEN && last_flag != BASE_START && last_flag != BASE_SELECT)
					tray_manager->closeDialog();
				else
				{
					flag = last_flag;
					tray_manager->closeDialog();
				}
			}
			else if (flag != BASE_PAUSE)
			{
				flag = BASE_PAUSE;
				tray_manager->showYesNoDialog(get_UTFtext("TEXT_EXITCAP"), get_UTFtext("TEXT_EXIT"));
			}
		
			return true;
		}
	OgreBites::Label* lb = (OgreBites::Label*) tray_manager->getWidget("fps_label");
	if (controler.get_keyboard()->isKeyDown(OIS::KC_F))
	{
		if (time <= 0.)
		{
			time = 0.2;
			sounder.play_once("hit");
			if (lb)
			{
				tray_manager->destroyWidget("fps_label");
				lb = NULL;
			}
			else
				lb = tray_manager->createLabel(OgreBites::TL_TOPLEFT, "fps_label", "", 100);
		}
	}
	
	if (last_flag == BASE_SELECT)
	{

		const int move_d = 500;
		if (controler.get_keyboard()->isKeyDown(OIS::KC_W))
		{
			cam->moveRelative(Vector3(0, 0, -ftime * move_d));
		}	
		
		if (controler.get_keyboard()->isKeyDown(OIS::KC_S))
		{
			cam->moveRelative(Vector3(0, 0, ftime * move_d));
		}
	
		if (controler.get_keyboard()->isKeyDown(OIS::KC_A))
		{
			cam->moveRelative(Vector3(-ftime * move_d, 0, 0));
		}
	
		if (controler.get_keyboard()->isKeyDown(OIS::KC_D))
		{
			cam->moveRelative(Vector3(ftime * move_d, 0, 0));
		}

		if (controler.get_keyboard()->isKeyDown(OIS::KC_UP))
		{
			cam->moveRelative(Vector3(0, ftime * move_d, 0));
		}	
		
		if (controler.get_keyboard()->isKeyDown(OIS::KC_DOWN))
		{
			cam->moveRelative(Vector3(0, -ftime * move_d, 0));
		}
	
		if (controler.get_keyboard()->isKeyDown(OIS::KC_LEFT))
		{
			cam->yaw(Degree(ftime * 30));
		}
	
		if (controler.get_keyboard()->isKeyDown(OIS::KC_RIGHT))
		{
			cam->yaw(Degree(-ftime * 30));
		}
	}

	if (lb)
	{
		lb->setCaption("fps=" + to_string((long long)window->getLastFPS()));
	}
	return true;
}

void Base::check_pos()
{
	Vector3 fp = scene_manager->getSceneNode("node_fighter")->getPosition();

	if (fp.y < SEA_LEVEL)
	{
		flag = BASE_PAUSE;
		last_flag = BASE_SELECT;
		tray_manager->showOkDialog(get_UTFtext("CAP_OVER1"), get_UTFtext("TEXT_OVER1"));
		sounder.stop_all();
		sounder.play_once("water");
	}

	if (fp.x > WORLD_SIZE)
		fp.x = WORLD_SIZE;

	if (fp.x < -WORLD_SIZE)
		fp.x = -WORLD_SIZE;

	if (fp.z > WORLD_SIZE)
		fp.z = WORLD_SIZE;

	if (fp.z < -WORLD_SIZE)
		fp.z = -WORLD_SIZE;

	if (fp.y > WORLD_SIZE)
		fp.y = WORLD_SIZE;

	scene_manager->getSceneNode("node_fighter")->setPosition(fp);

	Real ht = MIN_LEVEL + terrain_group->getHeightAtWorldPosition(fp);
	if (fp.y < ht)
	{
		
		flag = BASE_PAUSE;
		last_flag = BASE_SELECT;
		tray_manager->showOkDialog(get_UTFtext("CAP_OVER2"), get_UTFtext("TEXT_OVER2"));
		sounder.stop_all();
		sounder.play_once("bomb");
	}
}


// Main loop funtion of frame event
bool Base::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
	if (last_flag == BASE_RESET && flag == BASE_OPEN)
	{
		get_focus();
		flag = BASE_PAUSE;
		last_flag = BASE_OPEN;
		tray_manager->showYesNoDialog(get_UTFtext("TEXT_CFGCAP"), get_UTFtext("TEXT_CFG"));
	}
	else if (last_flag == BASE_RESET && flag == BASE_START)
	{
		get_focus();
		flag = BASE_PAUSE;
		last_flag = BASE_START;
		tray_manager->showYesNoDialog(get_UTFtext("TEXT_CFGCAP"), get_UTFtext("TEXT_CFG"));
		
		open_scene();
		open_scene(true);
	}
	else if (last_flag == BASE_RESET && flag == BASE_HELP)
	{
		get_focus();
		flag = BASE_HELP;
	}
	else if (last_flag == BASE_RESET) // default reset action
	{
		get_focus();
		flag = BASE_OPEN;
	}

	
	if (flag == BASE_HELP)
	{
		
		if (cv_thread != NULL)
		{
			TerminateThread(cv_thread, 0);
			CloseHandle(cv_thread);
			cv_thread = NULL;
		}

		// TODO
		/*

		help_scene();

		*/
		tray_manager->showOkDialog("test", "help scene should be done here.");
		last_flag = BASE_OPEN;
		flag = BASE_PAUSE;
	}
	
	if (flag == BASE_EXIT)
	{
		
		if (cv_thread != NULL)
		{
			TerminateThread(cv_thread, 0);
			CloseHandle(cv_thread);
			cv_thread = NULL;
		}

		return false;
	}
	
	if (flag == BASE_RESET)
	{
		
		if (cv_thread != NULL)
		{
			TerminateThread(cv_thread, 0);
			CloseHandle(cv_thread);
			cv_thread = NULL;
		}

		if (first_run)
			terrain_group->saveAllTerrains(true, true);
		return false;
	}

	if (flag == BASE_OPEN)
	{
		if (cv_thread != NULL)
		{
			TerminateThread(cv_thread, 0);
			CloseHandle(cv_thread);
			cv_thread = NULL;
		}

		if (tray_manager->getWidget("score_label"))
		{
			tray_manager->destroyWidget("score_label");
		}

		if (last_flag == BASE_START)
			start_scene(true);
		open_scene();
		if (first_run)
		{
			last_flag = BASE_HELP;
			flag = BASE_RESET;
		}
		else
		{
			last_flag = BASE_OPEN;
			flag = BASE_SELECT;
		}
	}
	
	if (flag == BASE_GAME_NORMAL || flag == BASE_GAME_PRO)
	{
		/* TODO
		tray_manager->createLabel(OgreBites::TL_BOTTOM, "game_vx", "", 200);
		tray_manager->createLabel(OgreBites::TL_BOTTOM, "game_vy", "", 200);
		tray_manager->createLabel(OgreBites::TL_BOTTOM, "game_vz", "", 200);
		//*/

		sounder.play_repeat("game");
		controler.get_kinetic()->setup(Vector3(0,0,-LIFT_OFF_SPEED));
		last_flag = flag;
		flag = BASE_MAINTAIN;
	}
	if (flag == BASE_START)
	{
		
		if (cv_thread != NULL)
		{
			TerminateThread(cv_thread, 0);
			CloseHandle(cv_thread);
			cv_thread = NULL;
		}

		if (tray_manager->getWidget("score_label"))
		{
			tray_manager->destroyWidget("score_label");
		}

		if (controler.get_capture() == NULL)
		{
			cv_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)cv_main,
                                   NULL, 0, NULL);
		}
		
		open_scene(true);
		select_scene(true);
		
		start_scene();
		reset_score();
		Vector3 vs = scene_manager->getSceneNode("node_fighter")->getPosition()
			+ Vector3(0,40,-2000);
		place_target(scene_manager, vs);
		scene_manager->getSceneNode("node_target")->setPosition(vs);

		controler.setoff_init(lock);
		last_flag = BASE_START;
		flag = BASE_MAINTAIN;
	}

	if (flag == BASE_SELECT)
	{
		if (tray_manager->getWidget("score_label"))
		{
			tray_manager->destroyWidget("score_label");
		}
		if (last_flag == BASE_START)
			start_scene(true);
		if (last_flag == BASE_GAME_NORMAL || last_flag == BASE_GAME_PRO)
		{
			/*
			game_scene(true);
			*/
		}
		select_scene();
		last_flag = BASE_SELECT;
		flag = BASE_MAINTAIN;
	}
	if (!work_basic(evt.timeSinceLastFrame))
		return false;
	if (flag == BASE_PAUSE)
		return true;
	
	if (flag == BASE_MAINTAIN)
	{
		static double stf_time = 0.;
		if (last_flag == BASE_SELECT)
		{
			cam->moveRelative(Vector3(30 * evt.timeSinceLastFrame, 0, 0));
			cam->lookAt(scene_manager->getSceneNode("node_fighter")->getPosition());
		}
		else if (last_flag == BASE_START)
		{
			stf_time = 0;
			Vector3 fp = scene_manager->getSceneNode("node_fighter")->getPosition();
			cam_move_near(cam,
						  fp + CAM_OFFSET,
						  evt.timeSinceLastFrame);
			cam->lookAt(fp);
			int ret = controler.setoff(controler.get_capture() != NULL, controler.get_keyboard(), evt.timeSinceLastFrame, lock);
			if (ret == 1) // Up
			{
				stf_time = 2;
				flag = BASE_GAME_PRO;
			}
			else if (ret == 2) // Right
			{
				stf_time = 2;
				flag = BASE_GAME_NORMAL;
			}

		}
		else if (last_flag == BASE_GAME_PRO)
		{
			if (!tray_manager->getWidget("score_label"))
			{
				tray_manager->createLabel(OgreBites::TL_TOPRIGHT, "score_label", 
					get_UTFtext("TEXT_SCORE")+"="+to_string((long long)show_score()), 200);
			}

			if (stf_time > 0)
			{
				scene_manager->getSceneNode("node_fighter")->translate(0,0,
					-5*LIFT_OFF_SPEED/2*(2-stf_time) * evt.timeSinceLastFrame);
				stf_time -= evt.timeSinceLastFrame;
				
				cam_move_near(cam, scene_manager->getSceneNode("node_fighter")->getPosition() 
					+ 2 * Vector3(0, 0, CAM_OFFSET.z), evt.timeSinceLastFrame,
					1.5f+(30-1.5)/2*(2-stf_time));
				
				cam->lookAt(scene_manager->getSceneNode("node_fighter")->getPosition());
			}
			else
			controler.drive(true, controler.get_capture() != NULL,
							scene_manager->getSceneNode("node_fighter"),
							controler.get_keyboard(), evt.timeSinceLastFrame, lock);

			Vector3 fp = scene_manager->getSceneNode("node_fighter")->getPosition();
			
			//cam->lookAt(fp);

			/* TODO
			((OgreBites::Label*)tray_manager->getWidget("game_vx"))->setCaption(
				to_string((long double)controler.get_kinetic()->get_velocity().x));
			
			((OgreBites::Label*)tray_manager->getWidget("game_vy"))->setCaption(
				to_string((long double)controler.get_kinetic()->get_velocity().y));
			
			((OgreBites::Label*)tray_manager->getWidget("game_vz"))->setCaption(
				to_string((long double)controler.get_kinetic()->get_velocity().z));
			//*/
			check_pos();

			if (hit_target(fp))
			{
				Vector3 vs(
					random(-WORLD_SIZE/2, WORLD_SIZE/2),
					random(-WORLD_SIZE/2, WORLD_SIZE/2),
					random(-WORLD_SIZE/2, WORLD_SIZE/2));

				place_target(scene_manager, 
					vs = Vector3(vs.x, terrain_group->getHeightAtWorldPosition(
					vs) + random(MIN_LEVEL + (10 - show_score()) * 1000, MAX_LEVEL - MIN_LEVEL),
					vs.z)
					);
				scene_manager->getSceneNode("node_target")->setPosition(vs);
				sounder.play_once("msg");
				increase_score();
				((OgreBites::Label*)tray_manager->getWidget("score_label"))->setCaption
					(get_UTFtext("TEXT_SCORE")+"="+to_string((long long)show_score()));
			}
		}

		else if (last_flag == BASE_GAME_NORMAL)
		{
			if (!tray_manager->getWidget("score_label"))
			{
				tray_manager->createLabel(OgreBites::TL_TOPRIGHT, "score_label", 
					get_UTFtext("TEXT_SCORE")+"="+to_string((long long)show_score()), 200);
			}

			if (stf_time > 0)
			{
				scene_manager->getSceneNode("node_fighter")->translate(0,0,
					-5*LIFT_OFF_SPEED/2*(2-stf_time) * evt.timeSinceLastFrame);
				stf_time -= evt.timeSinceLastFrame;

				
				cam_move_near(cam,
					  scene_manager->getSceneNode("node_fighter")->getPosition() + 
					  CAM_OFFSET, evt.timeSinceLastFrame, 1.5f+(14-1.5)/2*(2-stf_time));
				cam->lookAt(scene_manager->getSceneNode("node_fighter")->getPosition());

			}
			else
			controler.drive(false, controler.get_capture() != NULL,
							scene_manager->getSceneNode("node_fighter"),
							controler.get_keyboard(), evt.timeSinceLastFrame, lock);
			Vector3 fp = scene_manager->getSceneNode("node_fighter")->getPosition();
			
			check_pos();
			
			if (hit_target(fp))
			{
				Vector3 vs(
					random(-WORLD_SIZE/2, WORLD_SIZE/2),
					random(-WORLD_SIZE/2, WORLD_SIZE/2),
					random(-WORLD_SIZE/2, WORLD_SIZE/2));

				place_target(scene_manager, 
					vs = Vector3(vs.x, terrain_group->getHeightAtWorldPosition(
					vs) + random(MIN_LEVEL + (10 - show_score()) * 1000, MAX_LEVEL - MIN_LEVEL),
					vs.z)
					);
				scene_manager->getSceneNode("node_target")->setPosition(vs);
				sounder.play_once("msg");
				increase_score();
				((OgreBites::Label*)tray_manager->getWidget("score_label"))->setCaption
					(get_UTFtext("TEXT_SCORE")+"="+to_string((long long)show_score()));
			}
			//cam->lookAt(fp);
		}
	}
	return true;
}

void Base::get_focus()
{
	HWND windowHnd;
	window->getCustomAttribute("WINDOW", &windowHnd);
	SetForegroundWindow(windowHnd);
	SetFocus(windowHnd);
	return;
}

INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
{
	Base* fay = new Base();
	try
	{
		lock = CreateMutex(NULL, false, "cv_lock");
		if (fay->init())
		{
			fay->work_exec_param(Ogre::String(strCmdLine));
			fay->main();
		}
	}
	catch (Exception e)
	{
		MessageBox(NULL, e.getFullDescription().c_str(), fay->get_text("ERROR").c_str(),
			MB_OK | MB_ICONERROR | MB_TASKMODAL);
	}

	char* par = fay->get_restart_param();
	delete fay;
	if (par)
	{
		HWND new_wnd = (HWND)ShellExecute(NULL, "open", THIS_FILE_NAME, par, NULL, SW_SHOW);
		SetForegroundWindow(new_wnd);
		SetFocus(new_wnd);
	}
	return 0;
}
